#pragma once


// Copyright (c) 2010 Manuel Peinado Gallego <manuel.peinado@gmail.com>
// Distributed under the MIT license

// MeshRenderPolicy.h : interface of the MeshRenderPolicy class and its various subclasses
//

// Available render policies:
// * NaiveMeshRenderPolicy (glBegin/glEnd, terribly slow)
// * DisplayListMeshRenderPolicy (glBegin/glEnd but with a display list)
// * VertexArrayMeshRenderPolicy
// * VertexBufferMeshRenderPolicy

#include "GL.h"

template<class Derived>
class MeshRenderPolicy
{
protected:
    Derived* self() { return (Derived*)this; }
    const Derived* self() const { return (Derived*)this; }

    size_t getNumTexUnits() const;

    std::vector<gx::Vec3f> computeFlippedNormals() const
    {
        const std::vector<gx::Vec3f>& normals = self()->getNormals();
        std::vector<gx::Vec3f> flippedNormals;
        flippedNormals.reserve(normals.size());
        for(size_t i = 0; i < normals.size(); ++i)
        {
            flippedNormals.push_back(-self()->getNormals()[i]);
        }
        return flippedNormals;
    }
};

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

template<class Derived>
class NaiveMeshRenderPolicy : MeshRenderPolicy<Derived>
{
public:
    void invalidateGlObjects() { }
    void initGlObjects(bool /*flipNormals*/) const { }
    void drawImpl(bool flipNormals) const;
private:
    void drawVertex(int vertIndex, bool flipNormals) const;
};

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

template<class Derived>
class DisplayListMeshRenderPolicy : NaiveMeshRenderPolicy<Derived>
{
public:
    DisplayListMeshRenderPolicy();

    void invalidateGlObjects();
    void initGlObjects(bool flipNormals) const;
    void drawImpl(bool flipNormals) const;

protected:

    mutable int m_glDisplayList;
    mutable int m_glDisplayListWithFlippedNormals;
};

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

template<class Derived>
class VertexArrayMeshRenderPolicy : MeshRenderPolicy<Derived>
{
public:
    void invalidateGlObjects();
    void initGlObjects(bool flipNormals) const;
    void drawImpl(bool flipNormals) const;
private:
    mutable std::vector<gx::Vec3f> m_flippedNormals;
};

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

template<class Derived>
class VertexBufferMeshRenderPolicy : MeshRenderPolicy<Derived>
{
public:
    VertexBufferMeshRenderPolicy();

    void invalidateGlObjects();
    void initGlObjects(bool flipNormals) const;
    void drawImpl(bool flipNormals) const;

protected:
    enum { VertexBuffer, NormalBuffer, NumBuffers };
    mutable GLuint m_flippedNormalsVboId;
    mutable GLuint m_vboIds[NumBuffers];
    mutable GLuint m_texVboIds[16]; // We expect much fewer than 16 to be used
};



// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

template<class Derived>
size_t MeshRenderPolicy<Derived>::getNumTexUnits() const
{
    if(self()->getNumTexCoordLayers() == 0)
    {
        return 0;
    }
    return std::max(self()->getNumTexCoordLayers(), 
                    self()->getRequiredTexCoords());
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

template<class Derived>
DisplayListMeshRenderPolicy<Derived>::DisplayListMeshRenderPolicy()
: m_glDisplayList(0),  m_glDisplayListWithFlippedNormals(0)
{
}

template<class Derived>
void DisplayListMeshRenderPolicy<Derived>::invalidateGlObjects()
{
    if(m_glDisplayList != 0)
    {
        glDeleteLists(m_glDisplayList, 1);
        m_glDisplayList = 0;
    }
    if(m_glDisplayListWithFlippedNormals != 0)
    {
        glDeleteLists(m_glDisplayListWithFlippedNormals, 1);
        m_glDisplayListWithFlippedNormals = 0;
    }
}

template<class Derived>
void DisplayListMeshRenderPolicy<Derived>::initGlObjects(bool flipNormals) const
{
    int& list = flipNormals ? m_glDisplayListWithFlippedNormals 
                            : m_glDisplayList;

    if(list != 0)
    {
        return;
    }

    // Render the mesh into a display list
    list = glGenLists(1); 
    glNewList(list, GL_COMPILE); 

    NaiveMeshRenderPolicy::drawImpl(flipNormals);

    glEndList();
}

template<class Derived>
void DisplayListMeshRenderPolicy<Derived>::drawImpl(bool flipNormals) const
{
    int& list = flipNormals ? m_glDisplayListWithFlippedNormals 
                            : m_glDisplayList;
    glCallList(list);
}


// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

template<class Derived>
void VertexArrayMeshRenderPolicy<Derived>::invalidateGlObjects()
{
    m_flippedNormals.clear();
}

template<class Derived>
void VertexArrayMeshRenderPolicy<Derived>::initGlObjects(bool flipNormals) const {

    if(flipNormals && m_flippedNormals.empty())
    {
        m_flippedNormals = computeFlippedNormals();
    }
}

template<class Derived>
void VertexArrayMeshRenderPolicy<Derived>::drawImpl(bool flipNormals) const
{
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);

    // specify the various arrays
    glVertexPointer(3, GL_FLOAT,  0, &self()->getVertices()[0]);
    
    if(flipNormals)
    {
        glNormalPointer(GL_FLOAT, 0, &m_flippedNormals[0]);
    }
    else
    {
        glNormalPointer(GL_FLOAT, 0, &self()->getNormals()[0]);
    }


    // Set all tex. coords for all units for this vertex (see comment above)
    size_t numTexUnits = getNumTexUnits();
    for(size_t texIndex = 0; texIndex < numTexUnits; ++texIndex)
    {
        size_t index = texIndex < self()->getNumTexCoordLayers() ? texIndex : 0;
        glClientActiveTexture(GL_TEXTURE0 + texIndex);
        const gx::Vec3f* tc = &self()->getTexCoordLayer(index)[0];
        glTexCoordPointer(3, GL_FLOAT, 0, tc);
    }

    // draw the geometry
    if(self()->isIndexed())
    {
        glDrawElements(GL_TRIANGLES, self()->getIndices().size(), 
                       GL_UNSIGNED_INT, &self()->getIndices()[0]);
    }
    else
    {
        glDrawArrays(GL_TRIANGLES, 0, self()->getVertices().size());
    }

    // deactivate vertex arrays
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}


// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

template<class Derived>
VertexBufferMeshRenderPolicy<Derived>::VertexBufferMeshRenderPolicy()
{
    m_vboIds[0] = 0;
    m_flippedNormalsVboId = 0;
}

template<class Derived>
void VertexBufferMeshRenderPolicy<Derived>::invalidateGlObjects()
{
    if(m_vboIds[0] != 0)
    {
        glDeleteBuffers(NumBuffers, m_vboIds);
        glDeleteBuffers(self()->getNumTexCoordLayers(), m_texVboIds);
        m_vboIds[0] = 0;
    }
    if(m_flippedNormalsVboId != 0)
    {
        glDeleteBuffers(1, &m_flippedNormalsVboId);
    }
}

template<class Derived>
void VertexBufferMeshRenderPolicy<Derived>::initGlObjects(bool flipNormals) const
{
    if(flipNormals && m_flippedNormalsVboId == 0)
    {
        glGenBuffers(1, &m_flippedNormalsVboId);
        glBindBuffer(GL_ARRAY_BUFFER, m_flippedNormalsVboId);
        std::vector<gx::Vec3f> flippedNormals = computeFlippedNormals();
        glBufferData(
            GL_ARRAY_BUFFER, flippedNormals.size() * sizeof(gx::Vec3f), 
            &flippedNormals[0], GL_STATIC_DRAW
        );
        CHECK_GL_ERRORS();
    }

    if(m_vboIds[0] != 0)
    {
        return;
    }
 

    if(self()->getNumTexCoordLayers() > 0)
    {
        ASSERT(self()->getNumTexCoordLayers() < 16);
        glGenBuffers(self()->getNumTexCoordLayers(), m_texVboIds);
    }
    
    for(size_t texIndex = 0; texIndex < self()->getNumTexCoordLayers(); ++texIndex)
    {
        glBindBuffer(GL_ARRAY_BUFFER, m_texVboIds[texIndex]);
        glBufferData(
            GL_ARRAY_BUFFER, 
            self()->getTexCoordLayer(texIndex).size() * sizeof(gx::Vec3f), 
            &self()->getTexCoordLayer(texIndex)[0], 
            GL_STATIC_DRAW
        );
    }

    glGenBuffers(NumBuffers, m_vboIds);
    
    glBindBuffer(GL_ARRAY_BUFFER, m_vboIds[VertexBuffer]);
    glBufferData(
        GL_ARRAY_BUFFER, 
        self()->getVertices().size() * sizeof(gx::Vec3f), 
        &self()->getVertices()[0], 
        GL_STATIC_DRAW
    );
    CHECK_GL_ERRORS();

    glBindBuffer(GL_ARRAY_BUFFER, m_vboIds[NormalBuffer]);
    glBufferData(
        GL_ARRAY_BUFFER, self()->getNormals().size() * sizeof(gx::Vec3f), 
        &self()->getNormals()[0], GL_STATIC_DRAW
    );
    CHECK_GL_ERRORS();
}

template<class Derived>
void VertexBufferMeshRenderPolicy<Derived>::drawImpl(bool flipNormals) const
{
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);

    // bind VBO for vertex array
    glBindBuffer(GL_ARRAY_BUFFER, m_vboIds[VertexBuffer]);         
    glVertexPointer(3, GL_FLOAT,  0, 0);

    glBindBuffer(GL_ARRAY_BUFFER, flipNormals ? m_flippedNormalsVboId :
                                                m_vboIds[NormalBuffer]);
    CHECK_GL_ERRORS();
    glNormalPointer(GL_FLOAT, 0, 0);
    CHECK_GL_ERRORS();

    // Set all tex. coords for all units for this vertex (see comment above)
    size_t numTexUnits = getNumTexUnits();
    for(size_t texIndex = 0; texIndex < numTexUnits; ++texIndex)
    {
        size_t index = texIndex < self()->getNumTexCoordLayers() ? texIndex : 0;
        GLuint vobId = m_texVboIds[index];
        
        glClientActiveTexture(GL_TEXTURE0 + texIndex);
        glBindBuffer(GL_ARRAY_BUFFER,  vobId);
        glTexCoordPointer(3, GL_FLOAT, 0, 0);
    }

    // draw the geometry
    if(self()->isIndexed())
    {
        glDrawElements(GL_TRIANGLES, self()->getIndices().size(), 
                       GL_UNSIGNED_INT, &self()->getIndices()[0]);
        CHECK_GL_ERRORS();
    }
    else
    {
        const std::vector<gx::Vec3f>& v = self()->getVertices();
        glDrawArrays(GL_TRIANGLES, 0, v.size());
        CHECK_GL_ERRORS();
    }

    // deactivate vertex array
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);

    // bind with 0, so, switch back to normal pointer operation
    glBindBuffer(GL_ARRAY_BUFFER, 0);

}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 


template<class Derived>
void NaiveMeshRenderPolicy<Derived>::drawVertex(int vertIndex, 
                                                bool flipNormals) const
{
    // It might happen that the material has more texture units than our
    // mesh has texture coord. lists (typically, meshes will only have 
    // one such list). In that case, we "extend" the set of tex. coords lists
    // so that the first list is repeated as many times as needed. If, for 
    // instance, the material has 3 units but we only have 2 tex. coord lists,
    // for the third unit we'll use the tex coords in the first list. One
    // exception is when we have 0 tex. coord. lists. In that case there's 
    // nothing to extend, and we simply refuse to use any tex. coords at all
    size_t numTexUnits = getNumTexUnits();


    // Set all tex. coords for all units for this vertex (see comment above)
    for(size_t texIndex = 0; texIndex < numTexUnits; ++texIndex)
    {
        size_t index = texIndex < self()->getNumTexCoordLayers() ? texIndex : 0;
        const TexCoordList& texCoords = self()->getTexCoordLayer(index);

        glMultiTexCoord4f(
            GL_TEXTURE0 + texIndex,
            texCoords[vertIndex].x, texCoords[vertIndex].y, 
            0.0f, 1.0f);
    }

    // Set normal for this vertex
    if(self()->hasNormals())
    {
        gx::Vec3f n = self()->getNormals()[vertIndex];
        if(flipNormals)
        {
            n = -n;
        }
        glNormal3fv(n.to_array());
    }

    // Set coordinates of this vertex
    glVertex3fv(self()->getVertices()[vertIndex].to_array());
}

template<class Derived>
void NaiveMeshRenderPolicy<Derived>::drawImpl(bool flipNormals) const
{

    glBegin(GL_TRIANGLES);

    if(self()->isIndexed())
    {
        size_t nbIndices = self()->getIndices().size();
        size_t nbTriangles = nbIndices / 3;
        for(size_t indexIndex = 0; indexIndex < nbTriangles * 3; ++indexIndex) 
        { 
            size_t index = self()->getIndices()[indexIndex];
            drawVertex(index, flipNormals);
        }
    }
    else
    {
        for(size_t vertIndex = 0; vertIndex < self()->getVertices().size(); ++vertIndex) 
        { 
            drawVertex(vertIndex, flipNormals);
        }
    }

    glEnd();
}


// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 



